/**
 * 
 */
package adnotatio.rdf;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author kotelnikov
 */
public class Model extends Entity {

    /**
     * 
     */
    private static final long serialVersionUID = -5451798404516717761L;

    private ID fID;

    /**
     * This hash contains all namespaces managed by this model
     */
    private Set fNamespaces = new HashSet();

    /**
     * The set of all property identifiers
     */
    private Set fProperties = new HashSet();

    /**
     * This object maps resource identifiers to corresponding resource objects ({@link Resource}
     * instances)
     */
    private Map fResources = new HashMap();

    /**
     * @param parentModel the parent model; it can be <code>null</code>
     * @param id the identifier of the model
     */
    public Model(Model parentModel, ID id) {
        super(parentModel);
        fID = id;
        registerNamespace(fID);
    }

    /**
     * @param visitor
     */
    public void accept(IValueVisitor visitor) {
        visitor.visit(this);
    }

    /**
     * @return the iD
     */
    public ID getID() {
        return fID;
    }

    /**
     * Returns a set of all namespaces managed by this model
     * 
     * @return a set of all namespaces managed by this model
     */
    public Set getNamespaces() {
        return new HashSet(fNamespaces);
    }

    /**
     * Returns a resource corresponding to the specified resource URL
     * 
     * @param resourceId the URL of the resource to return
     * @param create if this flag is <code>true</code> and there is no
     *        resources with the given URL then this method will create and
     *        return a new resource
     * @return a resource corresponding to the specified resource URL
     */
    public Resource getResource(ID resourceId, boolean create) {
        Resource resource = (Resource) fResources.get(resourceId);
        if (resource == null && create) {
            // This constructor will automatically "re-map" this newly created
            // resource to the given identifier. It is done in the
            // Resource#setValue(...) method called by the constructor.
            resource = new Resource(this, resourceId);
        }
        return resource;
    }

    /**
     * Returns a resource corresponding to the specified resource URL
     * 
     * @param url the URL of the resource to return
     * @param create if this flag is <code>true</code> and there is no
     *        resources with the given URL then this method will create and
     *        return a new resource
     * @return a resource corresponding to the specified resource URL
     */
    public Resource getResource(String url, boolean create) {
        return getResource(new ID(url), create);
    }

    /**
     * @return an iterator over all resources managed by this
     */
    public Iterator getResourceIterator() {
        List list = new ArrayList(fResources.values());
        return list.iterator();
    }

    /**
     * @return the total number of resources managed by this model
     */
    public int getResourceNumber() {
        return fResources.size();
    }

    /**
     * @see adnotatio.rdf.Entity#getStringValue()
     */
    protected String getStringValue() {
        return fID.getURL();
    }

    /**
     * Returns a newly created container
     * 
     * @return a newly created container
     */
    public Container newContainer() {
        return new Container(this, new ID());
    }

    /**
     * @param value the value for which the corresponding literal should be
     *        created
     * @return a newly created literal
     */
    public Literal newLiteral(Object value) {
        return new Literal(this, value);
    }

    /**
     * Registers the namespace corresponding to the given identifier in the
     * internal map
     * 
     * @param id the identifier used as a provider of a namespace
     */
    private void registerNamespace(ID id) {
        String namespace = id.getNamespace();
        fNamespaces.add(namespace);
    }

    /**
     * Registers an object with the specified identifier as a property.
     * 
     * @param propertyID the identifier of the property to register
     */
    void registerProperty(ID propertyID) {
        // This method will register the namespace corresponding to the property
        // getResource(propertyID, true);
        fProperties.add(propertyID);
    }

    /**
     * Changes the identifier of a resource. This method is called by the
     * {@link Resource#setID(ID)} method to change the internal mapping
     * id/resource.
     * 
     * @param oldResourceId the old resource identifier
     * @param newResourceId the new resource identifier
     * @param resource the resource to re-map
     */
    void remapResource(ID oldResourceId, ID newResourceId, Resource resource) {
        if (oldResourceId != null) {
            fResources.remove(oldResourceId);
        }
        fResources.put(newResourceId, resource);
        registerNamespace(newResourceId);
    }

    /**
     * Removes the specified resource from the model
     * 
     * @param resource the resource to remove
     */
    void removeResource(Resource resource) {
        fResources.remove(resource.getID());
    }

}
